/**
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package in.shadowfax.proswipebutton;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;

/**
 * Loading status Progressbar
 * <p>
 * The Component has two custom attributes,
 * the color of the ring and the size of the border of the ring;
 * the attribute values are: "border_color" and "border_width"
 */
public class CircleProgressbar extends Component implements Component.DrawTask, Component.EstimateSizeListener, Component.BindStateChangedListener {

    private static final int DURATION = 1333;

    private final Paint mPaint = new Paint();

    private Color mProgressBorderColor = new Color(Color.getIntColor("#ff00ff"));

    private int mBorderWidth = 20;

    private final RectFloat mContentRectF = new RectFloat();

    private final RingPathTransform mRingPathTransform = new RingPathTransform();

    private final RingRotation mRingRotation = new RingRotation();

    private final Arc arc = new Arc();

    private AnimatorGroup animatorGroup;
    private AnimatorValue animatorRotate;

    public CircleProgressbar(Context context) {
        this(context, null);
    }

    public CircleProgressbar(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public CircleProgressbar(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);

        if (attrSet != null) {
            attrSet.getAttr("border_width").ifPresent(attr -> mBorderWidth = attr.getDimensionValue());
            attrSet.getAttr("border_color").ifPresent(attr -> mProgressBorderColor = attr.getColorValue());
        }

        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        mPaint.setStrokeWidth(mBorderWidth);
        mPaint.setStrokeCap(Paint.StrokeCap.SQUARE_CAP);
        mPaint.setStrokeJoin(Paint.Join.MITER_JOIN);
        mPaint.setColor(mProgressBorderColor);

        addDrawTask(this);
        setEstimateSizeListener(this);
        setBindStateChangedListener(this);
    }

    /**
     * 设置进度条的颜色
     */
    public void setProgressColor(int color) {
        this.mProgressBorderColor = new Color(color);
        mPaint.setColor(mProgressBorderColor);
    }

    /**
     * 设置进度条的颜色
     */
    public void setProgressColor(Color color) {
        this.mProgressBorderColor = color;
        mPaint.setColor(mProgressBorderColor);
    }

    /**
     * 设置进度条border的宽
     *
     * @param borderWidth border的宽值
     */
    public void setBorderWidth(int borderWidth) {
        this.mBorderWidth = borderWidth;
        mPaint.setStrokeWidth(mBorderWidth);
    }

    @Override
    public boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {
        int width = EstimateSpec.getSize(widthEstimateConfig);
        int height = EstimateSpec.getSize(heightEstimateConfig);
        setEstimatedSize(
                EstimateSpec.getSizeWithMode(width, EstimateSpec.NOT_EXCEED),
                EstimateSpec.getSizeWithMode(height, EstimateSpec.NOT_EXCEED));

        int mRadius = width > height ? height / 2 : width / 2;
        // Calculate the area to be drawn
        mContentRectF.modify((float) (width / 2.0 - mRadius + mBorderWidth / 2.0), (float) (height / 2.0 - mRadius + mBorderWidth / 2.0),
                (float) (width / 2.0 + mRadius - mBorderWidth / 2.0), (float) (height / 2.0 + mRadius - mBorderWidth / 2.0));

        return true;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        drawIntermediateProgress(canvas);
    }

    /**
     * Draw transition progress bar
     */
    private void drawIntermediateProgress(Canvas canvas) {
        int saveCount = canvas.save();
        canvas.rotate(mRingRotation.mRotation, mContentRectF.getHorizontalCenter(), mContentRectF.getVerticalCenter());

        // startAngle starts at 3 o'clock on a watch.
        double startAngle = (-90 + 360 * (mRingPathTransform.mTrimPathOffset
                + mRingPathTransform.mTrimPathStart));
        double sweepAngle = (360 * (mRingPathTransform.mTrimPathEnd
                - mRingPathTransform.mTrimPathStart));
        arc.setArc((float) startAngle, (float) sweepAngle, false);
        canvas.drawArc(mContentRectF, arc, mPaint);
        canvas.restoreToCount(saveCount);
    }


    @Override
    public void onComponentBoundToWindow(Component component) {
        startAnim();
    }

    @Override
    public void onComponentUnboundFromWindow(Component component) {
        stopAnim();
    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        if (isComponentDisplayed()) {
            startAnim();
        } else {
            stopAnim();
        }
    }

    private void startAnim() {
        if (animatorGroup == null) {
            animatorGroup = new AnimatorGroup();


            AnimatorValue trimPathStartAnimator = new AnimatorValue();
            trimPathStartAnimator.setDuration(DURATION);
            trimPathStartAnimator.setCurveType(Animator.CurveType.CUBIC_BEZIER_ACCELERATION);
            trimPathStartAnimator.setLoopedCount(Animator.INFINITE);
            trimPathStartAnimator.setValueUpdateListener((animatorValue, v) -> mRingPathTransform.mTrimPathStart = UiUtils.calAnimValue(0, 0.75, v));

            AnimatorValue trimPathEndAnimator = new AnimatorValue();
            trimPathEndAnimator.setDuration(DURATION);
            trimPathEndAnimator.setCurveType(Animator.CurveType.CUBIC_BEZIER_DECELERATION);
            trimPathEndAnimator.setLoopedCount(Animator.INFINITE);
            trimPathEndAnimator.setValueUpdateListener((animatorValue, v) -> mRingPathTransform.mTrimPathEnd = UiUtils.calAnimValue(0, 0.75, v));

            AnimatorValue trimPathOffsetAnimator = new AnimatorValue();
            trimPathOffsetAnimator.setDuration(DURATION);
            trimPathOffsetAnimator.setCurveType(Animator.CurveType.LINEAR);
            trimPathOffsetAnimator.setLoopedCount(Animator.INFINITE);
            trimPathOffsetAnimator.setValueUpdateListener((animatorValue, v) -> mRingPathTransform.mTrimPathOffset = UiUtils.calAnimValue(0, 0.25, v));

            animatorGroup.runParallel(trimPathStartAnimator, trimPathEndAnimator, trimPathOffsetAnimator);
        }
        if (animatorRotate == null) {
            animatorRotate = new AnimatorValue();
            animatorRotate.setDuration(6665);
            animatorRotate.setCurveType(Animator.CurveType.LINEAR);
            animatorRotate.setLoopedCount(Animator.INFINITE);
            animatorRotate.setValueUpdateListener((animatorValue, v) -> {
                mRingRotation.mRotation = (float) UiUtils.calAnimValue(0, 720, v);
                invalidate();
            });
        }
        animatorGroup.cancel();
        animatorGroup.start();
        animatorRotate.cancel();
        animatorRotate.start();
    }

    private void stopAnim() {
        if (animatorGroup != null) {
            animatorGroup.stop();
        }
    }

    private static class RingPathTransform {

        public double mTrimPathStart;
        public double mTrimPathEnd;
        public double mTrimPathOffset;
    }

    private static class RingRotation {

        private float mRotation;
    }
}
